package top.xia17.plugins.fast.common.database.redisson.lock;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.lang.NonNull;
import top.xia17.plugins.fast.common.exceptions.BadConfigException;
import top.xia17.plugins.fast.common.spring.aop.AnnotationMethodPoint;

/**
 * redisson分布式锁aop配置
 * 在方法上加上注解 @RSync 即可使用
 * 注意：如果是方法内嵌套调用，需要使用 AopContext.currentProxy()来获取代理对象，在调用嵌套方法。
 * @author xia17
 * @date 2022/9/15
 */
@Slf4j
public class RedissonLockPointAdvisor extends AbstractPointcutAdvisor {

    private final Pointcut pointcut;

    private final MethodInterceptor methodInterceptor;

    public RedissonLockPointAdvisor(RedissonLockProperties properties, RedissonClient redissonClient) {
        this.pointcut = new AnnotationMethodPoint(RSync.class);
        this.methodInterceptor = new RedissonLockMethodInterceptor(redissonClient,properties);
    }

    @Override
    @NonNull
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    @NonNull
    public Advice getAdvice() {
        return this.methodInterceptor;
    }


    private record RedissonLockMethodInterceptor(RedissonClient redissonClient,
                                                 RedissonLockProperties properties) implements MethodInterceptor {

            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                RSync sync = invocation.getMethod().getAnnotation(RSync.class);
                RLock lock = obtainLock(invocation, sync);
                try {
                    if (!doLock(lock, sync)) {
                        outLog("redisson分布式锁{}当前被占用，已放弃获取", lock.getName());
                        return null;
                    }
                    outLog("redisson分布式锁{}已被成功获取", lock.getName());
                    return invocation.proceed();
                } finally {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                        outLog("redisson分布式锁{}已被成功释放", lock.getName());
                    }
                }
            }


            /**
             * 加锁
             *
             * @param lock 锁对象
             * @param sync 锁的配置信息
             * @return /
             * @throws InterruptedException /
             */
            private boolean doLock(RLock lock, RSync sync) throws InterruptedException {
                boolean havaTry = sync.useGlobal() ? properties.getHavaTry() : sync.havaTry();
                if (havaTry) {
                    //  waitTime – 获取锁的最长时间; leaseTime ——租用时间; unit - 时间单位;
                    return lock.tryLock(sync.useGlobal() ? properties.getWaitTime() : sync.waitTime(),
                            sync.useGlobal() ? properties.getLeaseTime() : sync.leaseTime(),
                            sync.useGlobal() ? properties.getTimeUnit() : sync.timeUnit());
                }
                lock.lock(sync.useGlobal() ? properties.getLeaseTime() : sync.leaseTime(),
                        sync.useGlobal() ? properties.getTimeUnit() : sync.timeUnit());
                return true;
            }

            /**
             * 获取锁
             *
             * @param invocation /
             * @param sync       /
             * @return /
             */
            private RLock obtainLock(MethodInvocation invocation, RSync sync) {
                int tagArgIndex = sync.argIndex();
                if (tagArgIndex > invocation.getArguments().length) {
                    log.error("argIndex大于参数数量");
                    throw new BadConfigException("argIndex大于参数数量");
                }
                String key = properties.getNamePrefix() + sync.value();
                if (tagArgIndex > 0) {
                    Object argument = invocation.getArguments()[tagArgIndex - 1];
                    key += argument == null ? "" : argument.toString();
                }
                // 获取锁
                return redissonClient.getLock(key);
            }

            /**
             * 打印日志
             *
             * @param s       /
             * @param objects /
             */
            private void outLog(String s, Object... objects) {
                switch (properties.getLogLevel()) {
                    case DEBUG -> log.debug(s, objects);
                    case INFO -> log.info(s, objects);
                    default -> {
                    }
                }
            }
        }

}
